using System;
using System.Collections;
using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Snarl;
using SnarlGMailNotifier.Classes.Animations;
using SnarlGMailNotifier.Classes.GMailFeed;
using SnarlGMailNotifier.Properties;
using System.Text.RegularExpressions;

namespace SnarlGMailNotifier.Forms
{
    public partial class FNotify : Form
    {
        [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
        private static extern IntPtr CreateRoundRectRgn
        (
            int nLeftRect, // x-coordinate of upper-left corner
            int nTopRect, // y-coordinate of upper-left corner
            int nRightRect, // x-coordinate of lower-right corner
            int nBottomRect, // y-coordinate of lower-right corner
            int nWidthEllipse, // height of ellipse
            int nHeightEllipse // width of ellipse
        );

        private delegate void RunAsyncWorker();
        private delegate void HideFormAnimated();

        private System.Timers.Timer m_timTimeout = new System.Timers.Timer();
        private System.Timers.Timer m_timFeedSwitcher = new System.Timers.Timer();

        private bool m_bFeedUpdated = false;
        private bool m_sDontSavePassword = false;
        private bool isManualCheck = false;

        private string m_sFeedUrl = "";
        private string m_sUsername = "";
        private string m_sPassword = "";

        private const int ERROR_GENERIC = 0;
        private const int ERROR_NO_CONNECTION = 1;
        private const int ERROR_INVALIDLOGIN = 2;
        private const int ERROR_OFFLINEMODE = 3;
        private const int ERROR_NOAUTHDATA = 4;

        private GMailFeed m_lastFeed;
        private int m_iCurrentFeedIndex = 0;

        private Snarl.WindowsMessage doof = Snarl.WindowsMessage.WM_USER + 58;
        private NativeWindowApplication.snarlMsgWnd snarlComWindow;
        static IntPtr myHwnd;
        private string iconPath = System.IO.Path.GetDirectoryName(Application.ExecutablePath) + "\\gmail-icon.png";
        private ArrayList alreadyShownNotifications = new ArrayList();

        public FNotify()
        {
            InitializeComponent();
            if (myHwnd == IntPtr.Zero)
            {
                snarlComWindow = new NativeWindowApplication.snarlMsgWnd();
                myHwnd = snarlComWindow.Handle;
            }
            SnarlConnector.RegisterConfig(myHwnd, "Snarl Gmail Notifier", doof, iconPath);
            SnarlConnector.RegisterAlert("Snarl Gmail Notifier", "New email");
            SnarlConnector.RegisterAlert("Snarl Gmail Notifier", "More than maximum number");

            this.Region = System.Drawing.Region.FromHrgn(
                CreateRoundRectRgn(
                    0,
                    0,
                    Width,
                    Height,
                    20,
                    20)); // adjust these parameters to get the look you want.
        }

        ~FNotify()
        {
            SnarlConnector.RevokeConfig(Handle);
        }

        private void FNotify_Load(object sender, EventArgs e)
        {
            this.SetNotifyIcon(false);

            this.Left = Screen.PrimaryScreen.WorkingArea.Right - this.Width;
            this.Top = Screen.PrimaryScreen.WorkingArea.Bottom - this.Height;

            this.LoadSettings();

            this.m_timTimeout.Elapsed += new System.Timers.ElapsedEventHandler(timTimeout_Elapsed);
            this.m_timTimeout.AutoReset = false;

            this.m_timFeedSwitcher.Elapsed += new System.Timers.ElapsedEventHandler(m_timFeedSwitcher_Elapsed);

            // update network status prior to checking feeds
            NetworkDetection.UpdateNetworkStatus();

            this.StartFeedCheck();
            this.m_timTimeout.Start();
        }

        private void gMailFeedViewer_Click(object sender, EventArgs e)
        {

        }

        private void ShowNextFeedItem()
        {
            gMailFeedViewer.FeedItem = this.m_lastFeed.FeedItems[this.m_iCurrentFeedIndex];
            gMailFeedViewer.FeedItemIndex = this.m_iCurrentFeedIndex + 1;
            gMailFeedViewer.NumberOfFeedItems = this.m_lastFeed.FeedItems.Count;

            this.m_iCurrentFeedIndex++;
        }

        private void ShowFeedFormAnimated()
        {
            if ((this.m_lastFeed != null) && (this.m_lastFeed.FeedItems.Count > 0))
            {
                int maximumNumberAtOnce = 30;
                int counter = 1;
                foreach (GMailFeedItem newEntry in this.m_lastFeed.FeedItems)
                {
                    if (!alreadyShownNotifications.Contains(newEntry.ID))
                    {
                        if (counter <= maximumNumberAtOnce)
                        {
                            int returnCode = Snarl.SnarlConnector.ShowMessageEx("New email", newEntry.Title, newEntry.Summary + "\n\n" + newEntry.Author.Name + " (" + newEntry.Author.EMail + ")", 10, iconPath, myHwnd, doof, "");
                            snarlComWindow.registerUrl(returnCode, newEntry.Link.Url.AbsoluteUri);
                            counter++;
                        }
                        else
                        {
                            int numberMoreMessages = this.m_lastFeed.FeedItems.Count - maximumNumberAtOnce;
                            int returnCode = Snarl.SnarlConnector.ShowMessageEx("More than maximum number", "And " + numberMoreMessages + " more messages...", "You have " + m_lastFeed.FeedItems.Count + " new emails in your GMail inbox", 10, iconPath, myHwnd, doof, "");
                            snarlComWindow.registerUrl(returnCode, newEntry.Link.Url.AbsoluteUri);
                            break;
                        }
                        alreadyShownNotifications.Add(newEntry.ID);
                    }

                }

            }
            this.m_timTimeout.Start();
        }
        private void HideFeedFormAnimated()
        {
            AnimationRunner runner = new AnimationRunner();

            runner.AnimationFinished += new AnimationFinishedDelegate(runnerFadeOut_AnimationFinished);

            runner.Animations.Add(
                new SlideAnimation(
                    1000,
                    SlideAnimation.SlideType.SlideOut,
                    SlideAnimation.SlideDirection.Bottom));

            runner.Animations.Add(
                new FadeAnimation(
                    1000,
                    1.0,
                    0.0));

            runner.StartAnimations(this);
        }

        void runnerFadeIn_AnimationFinished(object sender, EventArgs e)
        {
            this.m_timFeedSwitcher.Start();
        }
        void runnerFadeOut_AnimationFinished(object sender, EventArgs e)
        {
            this.m_timTimeout.Start();
        }

        void timTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.StartFeedCheck();
        }
        void m_timFeedSwitcher_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.m_iCurrentFeedIndex == this.m_lastFeed.FeedItems.Count)
            {
                this.m_timFeedSwitcher.Stop();
                this.HideDelegatedFormAnimated();
            }
            else
                this.ShowNextFeedItem();
        }

        /// <summary>
        /// Tries to run the BackgroundWorker-Process. If an invoke is required (thread-safe) then a delegate is instanced and tried again.
        /// </summary>
        void StartFeedCheck()
        {
            if (this.InvokeRequired)
            {
                RunAsyncWorker d = new RunAsyncWorker(this.StartFeedCheck);
                this.Invoke(d);
            }
            else
            {
                if (!bgWorker.IsBusy)
                {
                    bgWorker.RunWorkerAsync();
                }
            }
        }

        void HideDelegatedFormAnimated()
        {
            if (this.InvokeRequired)
            {
                HideFormAnimated d = new HideFormAnimated(this.HideFeedFormAnimated);
                this.Invoke(d);
            }
            else
                this.HideFeedFormAnimated();
        }


        /* ToolStripMenu Items */

        private void beendenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SnarlConnector.RevokeConfig(myHwnd);
            Application.Exit();
        }

        private void einstellungenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FSettings settings = new FSettings();
            settings.ShowDialog();

            if (settings.DialogResult == DialogResult.OK)
            {
                this.LoadSettings();
                StartFeedCheck();
            }
        }

        private void checkMailToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.isManualCheck = true;
            this.StartFeedCheck();
        }

        private void tellMeAgainToolStripItem_Click(object sender, EventArgs e)
        {
            alreadyShownNotifications.Clear();
            this.isManualCheck = true;
            this.StartFeedCheck();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox aboutbox = new AboutBox();
            aboutbox.ShowDialog();
        }

        /// <summary>
        /// Excutes the logic to fetch the GMail feed run the fade in effect if the feed differs from the last one
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            GMailFeed currentFeed = null;
            this.m_bFeedUpdated = false;

            if (authenticate())
            {
                if (NetworkDetection.NetworkIsAvailable())
                {
                    try
                    {
                        currentFeed =
                            new GMailFeed(
                                new Uri(this.m_sFeedUrl),
                                this.m_sUsername,
                                this.m_sPassword);
                    }
                    #region catch exceptions
                    catch (WebException ex)
                    {
                        if (ex.Response == null)
                        {
                            displayErrorMessage(ERROR_NO_CONNECTION);

                        }
                        else if (ex.Response.GetType() == typeof(HttpWebResponse))
                        {
                            HttpWebResponse httpResponse = (HttpWebResponse)ex.Response;

                            if (httpResponse.StatusCode == HttpStatusCode.Unauthorized)
                            {
                                displayErrorMessage(ERROR_INVALIDLOGIN);
                            }
                        }
                        else
                        {
                            displayErrorMessage(ERROR_GENERIC);
                        }
                    }
                    catch (Exception ex)
                    {
                        displayErrorMessage(ERROR_GENERIC, ex.ToString());
                    }
                    #endregion

                    // show updates of feed if there are changes
                    if (this.m_lastFeed == null || this.isManualCheck || !this.m_lastFeed.Equals(currentFeed))
                    {
                        this.isManualCheck = false;
                        this.m_lastFeed = currentFeed;
                        this.m_bFeedUpdated = true;

                        if (currentFeed != null)
                        {
                            this.SetNotifyIcon(currentFeed.FeedItems.Count > 0);
                        }
                    }
                    // (re)start timer if it was disabled somewhere else
                    this.m_timTimeout.Start();
                }
                else
                {
                    displayErrorMessage(ERROR_OFFLINEMODE);

                    // sleep until network is up again
                    this.m_timTimeout.Stop();
                    // tell networkdetector to wake me up if online again
                    // returns a bool if successful, for now not cared about, cause we have always only one instance of FNotify (later: multiple accounts?)
                    NetworkDetection.wakeMeIfNetworkIsUpAgain(this);
                }
            }
        }

        private void displayErrorMessage(int errorCode)
        {
            displayErrorMessage(errorCode, "");
        }

        private void displayErrorMessage(int errorCode, String additionalText)
        {
            String title = "";
            String message = "";

            switch (errorCode)
            {
                case ERROR_OFFLINEMODE:
                    title = "Offline mode";
                    message = "Your mail will not be fetched until you are back online." + " " + additionalText;
                    break;
                case ERROR_INVALIDLOGIN:
                    title = "Login credentials incorrect";
                    message = "Correct your username and password. (Right-click the GMail icon in the notification area and click on Settings)" + " " + additionalText;
                    break;
                case ERROR_NO_CONNECTION:
                    title = "Could not connect";
                    message = "Check your username and password. (Right-click the GMail icon in the notification area and click on Settings)" + " " + additionalText;
                    break;
                case ERROR_GENERIC:
                    title = "Could not fetch your GMail";
                    message = "There was a server error while fetching your mail. GMail Notifier will try again later." + " " + additionalText;
                    break;
                case ERROR_NOAUTHDATA:
                    title = "No account data";
                    message = "Set your username and password. (Right-click the GMail icon in the notification area and click on Settings)";
                    break;
            }

            Snarl.SnarlConnector.ShowMessage(title, message, 10, iconPath, myHwnd, doof);
        }

        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (this.m_bFeedUpdated)
                this.ShowFeedFormAnimated();
        }

        private bool authenticate()
        {
            while (!authDataIsValid())
            {
                // display credentials dialog to gather username and/or password
                FEnterCredentials credentials = new FEnterCredentials();
                credentials.ShowDialog();

                if (credentials.DialogResult == DialogResult.OK)
                {
                    this.m_sUsername = credentials.Username.Trim();
                    this.m_sPassword = credentials.Password.Trim();
                }
                // user canceled dialog
                else
                {
                    displayErrorMessage(ERROR_NOAUTHDATA);
                    m_timTimeout.Stop();
                    return false;
                }
            }
            return true;
        }

        private bool authDataIsValid()
        {
            if (IsValidEmail(m_sUsername) && m_sPassword != "")
            {
                return true;
            }

            return false;

        }

        public static bool IsValidEmail(string strIn)
        {
            // Return true if strIn is in valid e-mail format.
            return Regex.IsMatch(strIn,
                   @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
                   @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
        }

        private void SetNotifyIcon(bool unreadMessages)
        {
            if (unreadMessages)
            {
                this.trayIcon.Icon = Properties.Resources.gmail_icon;
                trayIcon.Text =
                    Application.ProductName +
                    " [" +
                    this.m_lastFeed.FeedItems.Count.ToString() + " ungelesene Nachricht" + (this.m_lastFeed.FeedItems.Count > 1 ? "en" : "") +
                    "]";
            }
            else
            {
                this.trayIcon.Icon = Properties.Resources.gmail_icon_greyscale;
                trayIcon.Text = Application.ProductName;
            }
        }

        private void LoadSettings()
        {
            this.m_sUsername = Settings.Default.GMailUsername.Trim();
            this.m_sFeedUrl = Settings.Default.GMailFeedUrl.Trim();

            if (IsValidEmail(m_sUsername))
            {
                string domain = this.m_sUsername.Split('@')[1];

                // change Feed URL for Google Apps users
                if (!(domain.Equals("gmail.com") || domain.Equals("googlemail.com")))
                {                  
                    this.m_sFeedUrl = Settings.Default.GoogleAppsBaseUrl.Trim() + domain + "/feed/atom";
                }
            }

            this.m_sPassword = Settings.Default.GMailPassword.Trim();
            this.m_sDontSavePassword = Settings.Default.SavePassword;
            this.m_timTimeout.Interval = (double)Settings.Default.NewMailCheckIntervalSeconds * 1000;
            this.m_timFeedSwitcher.Interval = (double)Settings.Default.PreviewVisibleInterval * 1000;
        }

        public void wakeMeUp()
        {
            StartFeedCheck();
        }
    }
}